if not InspectEquip then return end

local IE = InspectEquip
local IS = InspectEquip_ItemSources
local L = LibStub("AceLocale-3.0"):GetLocale("InspectEquip")

local band = bit.band
local bor = bit.bor
local tinsert = table.insert
local strfind = string.find

-- Database
InspectEquipLocalDB = {}

-- GUI
local bar, barText
local coUpdate

function IE:InitLocalDatabase()
	if not InspectEquipLocalDB then InspectEquipLocalDB = {} end
	setmetatable(InspectEquipLocalDB, {__index = {
		Zones = {}, Bosses = {}, Items = {}
	}})
	
	local _, currentBuild = GetBuildInfo()
	
	-- create database if not present or outdated (or wrong locale)
	if (InspectEquipLocalDB.ClientBuild ~= currentBuild) or (InspectEquipLocalDB.Locale ~= GetLocale()) then
		self:CreateLocalDatabase()
	end
end

local function createUpdateGUI()

	bar = CreateFrame("STATUSBAR", nil, UIParent, "TextStatusBar")
	bar:SetWidth(300)
	bar:SetHeight(30)
	bar:SetPoint("CENTER", 0, -100)
	bar:SetBackdrop({
		bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
		edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
		tile = 1, tileSize = 10, edgeSize = 10,
		insets = {left = 1, right = 1, top = 1, bottom = 1}
    })
	bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
	bar:SetBackdropColor(0, 0, 0, 1)
    bar:SetStatusBarColor(0.6, 0.6, 0, 0.4)
    bar:SetMinMaxValues(0, 100)
	bar:SetValue(0)
	
	barText = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    barText:SetPoint("CENTER", bar, "CENTER")
    barText:SetJustifyH("CENTER")
    barText:SetJustifyV("CENTER")
    barText:SetTextColor(1, 1, 1)
    barText:SetText("InspectEquip: " .. L["Updating database..."])
	
end

local function EndUpdate()
	bar:SetScript("OnUpdate", nil)
	bar:Hide()
	coUpdate = nil
end

local function UpdateTick()
	if coUpdate then
		local ok, msg = coroutine.resume(coUpdate)
		if not ok then
			EndUpdate()
			message("[InspectEquip] Could not update database: " .. msg)
		end
	end
end

local function UpdateBar()
	bar:SetValue(bar:GetValue() + 1)
	coroutine.yield()
end

local function GetReverseMapping(baseMap)
	local map = {}
	local id, name
	for id, name in pairs(baseMap) do
		map[name] = id
	end
	return map
end

local function DifficultyToMode(diff, raid)
	if diff == 1 then
		-- 5 normal / 10 normal
		return raid and 8 or 2
	elseif diff == 2 then
		-- 5 heroic / 25 normal
		return raid and 16 or 1
	elseif diff == 3 then
		-- 10 heroic
		return 32
	elseif diff == 4 then
		-- 25 heroic
		return 64
	elseif diff == 5 then
		-- lfr
		return 128
	end
end

local function AddToDB(tempDB, itemID, zoneID, bossID, mode)
	local sources = tempDB[itemID]
	local entry
	if sources then
		for _, entry in pairs(sources) do
			if (entry[1] == zoneID) and (entry[2] == bossID) then
				entry[3] = bor(entry[3], mode)
				return
			end
		end
		tinsert(sources, {zoneID, bossID, mode})
	else
		tempDB[itemID] = {{zoneID, bossID, mode}}
	end
end

local function SaveToDB(tempDB, entryType)
	local itemID, sources, entry
	for itemID, sources in pairs(tempDB) do
		local str = InspectEquipLocalDB.Items[itemID]
		local isEntry = IS.Items[itemID]
		
		-- loop through sources we found
		for _, entry in pairs(sources) do
			local entryStr = entryType .. "_" .. entry[1] .. "_" .. entry[3] .. "_" .. entry[2]
			
			-- skip if already in IS DB
			if not (isEntry and (strfind(";" .. isEntry .. ";", ";" .. entryStr .. ";"))) then
				if str then
					str = str .. ";" .. entryStr
				else
					str = entryStr
				end
			end
			
		end
		
		InspectEquipLocalDB.Items[itemID] = str
	end
end

local function UpdateFunction()

	-- init/reset database
	local db = InspectEquipLocalDB
	db.Zones = {}
	db.Bosses = {}
	db.Items = {}
	db.NextZoneID = 1000
	db.NextBossID = 1000
	
	-- get instance count
	local insID, insName, diff
	local i = 1
	local j
	repeat
		insID, insName = EJ_GetInstanceByIndex(i, true)
		i = i + 1
	until not insID
	local insCount = i - 2
	
	bar:SetMinMaxValues(0, insCount + 2)
	
	-- get IS mapping for zone/boss name -> zone/boss id
	local zoneMap = GetReverseMapping(IS.Zones)
	local bossMap = GetReverseMapping(IS.Bosses)
	
	UpdateBar()
	
	-- temp db to allow for merging of modes
	local tempDB = {}

	-- get loot!
	i = 1
	repeat
		insID, insName = EJ_GetInstanceByIndex(i, true)
		if not insID then break end
		
		-- get zone id for db
		local zoneID = zoneMap[insName]
		if not zoneID then
			-- zone is not in db
			zoneID = db.NextZoneID
			db.Zones[zoneID] = insName
			zoneMap[insName] = zoneID
			db.NextZoneID = db.NextZoneID + 1
		end
		
		EJ_SelectInstance(insID)
		for diff = 1, 5 do
			if EJ_IsValidInstanceDifficulty(diff) then
				EJ_SetDifficulty(diff)
				local mode = DifficultyToMode(diff, true)
				local n = EJ_GetNumLoot()
				for j = 1, n do
					-- get item info
					local itemName, _, _, _, itemID, itemLink, encID = EJ_GetLootInfoByIndex(j)
					local encName = EJ_GetEncounterInfo(encID)
					
					-- get boss id for db
					local bossID = bossMap[encName]
					if not bossID then
						-- boss is not in db
						bossID = db.NextBossID
						db.Bosses[bossID] = encName
						bossMap[encName] = bossID
						db.NextBossID = db.NextBossID + 1
					end
					
					-- add item to db
					AddToDB(tempDB, itemID, zoneID, bossID, mode)
					
				end
			end
		end
		
		UpdateBar()
		i = i + 1
	until not insID
	
	-- save to db
	SaveToDB(tempDB, "r")
	local _, currentBuild = GetBuildInfo()
	db.ClientBuild = currentBuild
	db.Locale = GetLocale()
	UpdateBar()
	
	-- Done
	EndUpdate()
	--IE:Print("Database updated!")
end

function IE:CreateLocalDatabase()

	-- load encounter journal
	if not IsAddOnLoaded("Blizzard_EncounterJournal") then
		local loaded, reason = LoadAddOn("Blizzard_EncounterJournal")
		if not loaded then
			message("[InspectEquip] Could not load encounter journal: " .. reason)
		end
	end
	
	-- show progress bar
	createUpdateGUI()
	bar:Show()
	
	-- start update
	coUpdate = coroutine.create(UpdateFunction)
	local ok, msg = coroutine.resume(coUpdate)
	if ok then
		bar:SetScript("OnUpdate", UpdateTick)
	else
		EndUpdate()
		message("[InspectEquip] Could not update database: " .. msg)
	end
	
end

